package com.hzw.saas.common.storage.util;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.Adler32;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.crypto.digest.DigestUtil;

/**
 * 校验和工具
 *
 * @author zzl
 * @since 06/21/2021
 */
public final class ChecksumUtil {

    // adler-32 算法模量
    public final static int MOD_ADLER = 65521;

    private final static int BUFFER_SIZE = 2 << 12;

    /**
     * Mark Adler的adler-32校验和算法通用实现
     * 亦可使用 java.util.zip.Adler32
     *
     * @param data   待计算字节数组
     * @param offset 偏移量
     * @param length 待计算长度
     * @return 校验和
     * @see Adler32
     */
    public static long adler32(byte[] data, int offset, int length) {
        checkBuffer(data, offset, length);
        long a = 1, b = 0;
        return adler32(a, b, data, offset, length);
    }

    public static long adler32(byte[] data, int length) {
        return adler32(data, 0, length);
    }

    public static long adler32(byte[] data) {
        return adler32(data, 0, data.length);
    }

    public static long adler32(File file) {
        if (!FileUtil.isFile(file))
            return -1L;
        try (InputStream ins = FileUtil.getInputStream(file);) {
            byte[] buffer = new byte[BUFFER_SIZE];
            int read = -1;
            long preAdler32 = 1;
            while ((read = ins.read(buffer)) != -1) {
                preAdler32 = adler32(preAdler32, buffer, 0, read);
            }
            return preAdler32;
        } catch (IOException e) {
            throw ExceptionUtil.wrapRuntime(e);
        }
    }

    /**
     * 计算32位MD5摘要值，并转为16进制字符串
     *
     * @param data   数据存放的数组
     * @param offset 数据在data中的起始位置
     * @param length 数据长度
     * @return 32位MD5摘要值的16进制字符串
     */
    public static String md5Hex(byte[] data, int offset, int length) {
        checkBuffer(data, offset, length);
        if (offset == 0 && length == data.length) {
            return DigestUtil.md5Hex(data);
        }
        byte[] tempData = new byte[length];
        System.arraycopy(data, offset, data, 0, length);
        return DigestUtil.md5Hex(tempData);
    }

    public static String md5Hex(File file) {
        if (!FileUtil.isFile(file))
            return null;
        return DigestUtil.md5Hex(file);
    }

    /**
     * Adler-32型式的rolling hash
     * 使用同一字节数组中某偏移量处的字节片段hash值计算下一偏移量字节片段的hash值
     *
     * @param oldAdler32  原字节片段hash
     * @param preByte     原字节片段首位字节
     * @param nextByte    下一偏移量字节片段末尾字节
     * @param bytesLength 字节片段长度
     * @return 下一偏移量字节片段hash值
     */
    public static long rollingHash(long oldAdler32, byte preByte, byte nextByte, int bytesLength) {
        long a = oldAdler32 & 0xffff;
        long b = (oldAdler32 >>> 16) & 0xffff;
        long preByteN = Byte.toUnsignedLong(preByte);
        long nextByteN = Byte.toUnsignedLong(nextByte);
        long an = a - preByteN + nextByteN;
        an = adler32Mod(an);
        long bn = b - 1 - preByteN * (long) bytesLength + an;
        bn = adler32Mod(bn);
        return ((bn << 16)) + (an & 0xffff);
    }

    public static long adler32(long preAdler32, byte[] data, int offset, int length) {
        long a = preAdler32 & 0xffff;
        long b = (preAdler32 >>> 16) & 0xffff;
        return adler32(a, b, data, offset, length);
    }

    private static long adler32(long a, long b, byte[] data, int offset, int length) {
        for (int i = offset; i < length; i++) {
            a += (data[i] & 0xff);
            a = adler32Mod(a);
            b += a;
            b = adler32Mod(b);
        }
        return b << 16 | a;
    }

    /**
     * 16位校验和取余
     *
     * @param checksum16Bit 16位校验和，
     * @return 余数
     */
    private static long adler32Mod(long checksum16Bit) {
        if (checksum16Bit < MOD_ADLER)
            return checksum16Bit;
        if (checksum16Bit < 2 * MOD_ADLER)
            return checksum16Bit - MOD_ADLER;
        return checksum16Bit % MOD_ADLER;
    }

    private static void checkBuffer(byte[] data, int offset, int length) {
        if (data == null) {
            throw new NullPointerException();
        }
        if (offset < 0 || length < 0 || (offset + length) > data.length) {
            throw new ArrayIndexOutOfBoundsException();
        }
    }

    /**
     * Suppress default constructor
     */
    private ChecksumUtil() {
        throw new UnsupportedOperationException("Private constructor, cannot be accessed.");
    }
}
